<template>
    <div class="container">
        <!-- 操作栏 -->
        <ActionBar />
        <!-- 画布 -->
        <div id="graphNode"></div>
        <!-- 配置 -->
        <Configure @change="changeNode" :node="state.graphNodeModel" @saveJsonInfo="saveJsonInfo"
            @exportImageDownload="exportImageDownload" :type="state.type" />
        <!-- 添加文字框 -->
        <el-dialog v-model="addText.visible" title="添加文字" width="30%">
            <el-input v-model="addText.text" autofocus></el-input>
            <template #footer>
                <span style="display:flex;justify-content: space-around;">
                    <el-button @click="addText.visible = false">Cancel</el-button>
                    <el-button type="primary" @click="saveText">Confirm</el-button>
                </span>
            </template>
        </el-dialog>
        <!-- 添加自定义数据 -->
        <el-dialog v-model="customForm.visible" title="添加自定义数据" width="40%">
            <el-form label-width="0px">
                <el-form-item v-for="(item,idx) in customForm.domain">
                    <div class="item">
                        <span class="label">key:</span>
                        <el-input v-model="item.key"></el-input>
                        <span class="label">value:</span>
                        <el-input v-model="item.value"></el-input>
                        <el-button style="margin-left: 10px;" type="danger" @click="removeCustomItem(idx)">删除
                        </el-button>
                    </div>
                </el-form-item>
            </el-form>
            <template #footer>
                <span style="display:flex;justify-content: space-around;">
                    <el-button @click="customForm.visible = false">Cancel</el-button>
                    <el-button @click="addCustomInfo">AddDomain</el-button>
                    <el-button type="primary" @click="saveCustomInfo">Confirm</el-button>
                </span>
            </template>
        </el-dialog>
        <!-- 查看自定义数据`` -->
        <el-dialog v-model="customTable.visible">
            <el-table :data="customTable.tableData" style="width: 100%">
                <el-table-column prop="key" label="key" />
                <el-table-column prop="value" label="value" />
            </el-table>
        </el-dialog>
    </div>
</template>

<script lang='ts' setup>
// 画布
// 引入操作栏
import ActionBar from '../ActionBar/index.vue'
// 引入配置栏
import Configure from '../Configure/index.vue'
import G6, { EdgeConfig, Graph, IG6GraphEvent, IGraph, INode, Item, NodeConfig } from '@antv/g6';
import {  Action, NodeItem,NodeItemInfo } from '../props'
import { onMounted, reactive, ref } from 'vue';
import { registerNode } from '../../utils/graph';
import { getNodeModel, updateShapePosition } from '../../utils/tool'

// 数据类型
interface IState {
    graph:Graph | null,
    sourceAnchorIdx:null | number
    targetAnchorIdx:null | number
    graphNode:any
    graphNodeModel:any
    type:string
}
const state = reactive<IState>({
    graph: null, // 实例
    graphNode: null, //  当前点击的节点实例
    graphNodeModel: {}, // 当前点击的节点的数据
    sourceAnchorIdx: null, // 边的起始点
    targetAnchorIdx: null, // 边的结束点
    type: "", // 当前点击的元素
})

// 初始化数据
const defaultData = () => {
    return {
        // nodes: [
        //     {
        //         id: "node1",
        //         label: "node1"
        //     },
        //     {
        //         id: "node2",
        //         label: "node2"
        //     }
        // ],
        // combos: [

        // ],
    }
}
// 初始化配置信息
const defaultConfig = () => {
    // 画布添加格子
    const grid = new G6.Grid()
    // 菜单
    const menu = defaultMenu()
    // 缩略图
    const map = new G6.Minimap({
        size: [200, 100]
    })
    // 默认配置
    const config = {
        container: 'graphNode',// 画布
        width: window.innerWidth,// 宽
        height: window.innerHeight,// 高
        fitViewPadding: 20,
        animate: true,// 是否开启动画
        // 动画配置
        animateCfg: {
            duration: 500,
            easing: 'easeLinear',
        },
        groupByTypes: false,
        // 布局
        layout: {
            type: 'dagre',
            // rankdir: 'LR',
            nodesep: 30,
            ranksep: 30,
        },
        // 支持的行为
        modes: {
            // 允许拖拽画布、缩放画布、拖拽节点
            default: [
                'drag-canvas', // 移动画布行为
                'zoom-canvas',// 缩放画布行为
                {   // 节点的移动行为
                    type: 'drag-node',
                    // 是否使用虚线移动
                    enableDelegate: false,
                    updateEdge: true,//是否在拖拽节点时更新所有与之相连的边，默认为 true 

                    // 是否允许移动节点
                    shouldBegin: (e:any) => {
                        // 点击锚点不允许移动节点
                        if (e.target.get('name') === 'node-shape') return false;
                        return true;
                    }
                },
                {
                    // 创建边
                    type: 'create-edge',
                    trigger: "drag", // 触发方式
                    // 是否允许创建边
                    shouldBegin: (e: any) => {
                        //  如果未点击锚点则不允许创建边    
                        if (e.target && e.target.get('name') !== 'node-shape') return false
                        // 获取边的起始位置
                        state.sourceAnchorIdx = e.target.get('anchorPointIdx')
                        // 缓存连接到此锚点圆的边数
                        e.target.set('links', e.target.get('links') + 1)
                        return true;
                    },
                    shouldEnd: (e: any) => {
                        // 判断边的结束点是否在锚点上
                        if (e.target && e.target.get('name') !== 'node-shape') return false

                        if (e.target) {
                            // 获取结束点
                            state.targetAnchorIdx = e.target.get('anchorPointIdx')
                            e.target.set('links', e.target.get('links') + 1)
                            return true;
                        }
                        state.targetAnchorIdx = null;
                        return true;
                    },
                },
                {
                    type: 'drag-combo',
                    enableDelegate: false,// 开启图形代理 delegate
                }
            ],
        },
        // 默认节点配置
        defaultNode: {
            type: 'rect',
            size: [50, 50],
            style: {
                radius: 10,
                fill: "#3081b3",
                stroke: "#049eff",
                lineWidth: 2,
                cursor: "pointer"
            },
            label: "",
            labelCfg: {
                style: {
                    fill: "#ffffff",
                    fontSize: 12
                }
            }
        },
        // 默认边配置
        defaultEdge: {
            // 默认边配置
            type: 'quadratic', // 二阶贝塞尔曲线
            label: "",
            labelCfg: {
                refX: 0,
                refY: 0,
                style: {
                    fill: "#000000", // 文本颜色
                    fontSize: 12 // 字体大小
                }
            },
            style: {
                stroke: '#F6BD16', // 颜色
                lineWidth: 2, // 边宽度
                endArrow: true, // 绘制箭头
                lineAppendWidth: 15,// 响应鼠标事件时的检测宽度
                cursor: "pointer"
            },
        },
        // 默认combo配置
        defaultCombo: {
            label: "",
            labelCfg: {
                refX: 0,
                refY: 0,
                style: {
                    fill: "#000",
                    fontSize: 12
                }
            },
            style: {
                fill: "#87CEFA",
                stroke: "#87CEFA",
                lineWidth: 1,
                fillOpacity: 0.6,
                cursor: 'move'
            }
        },
        // 注册插件
        plugins: [grid, menu, map],
    }
    return config
}
// 上下文菜单
const defaultMenu = () => {
    return new G6.Menu({
        offsetX: -20,
        offsetY: -50,
        itemTypes: ['node', 'edge', 'combo'],
        getContent(evt: IG6GraphEvent | undefined):HTMLDivElement | string {
            let type = evt?.item!.getType()
            const outDiv = document.createElement('div');
            outDiv.style.width = '80px';
            outDiv.style.cursor = 'pointer';
            outDiv.innerHTML = `
            <p class="menuNode" id="delete">删除节点</p>
            ${['node'].includes(type || 'node') ? ' <p class="menuNode" id="copy">复制节点</p>' : ''}
            ${['node', 'edge'].includes(type || 'node') ? ' <p class="menuNode" id="add">自定义数据</p><p class="menuNode" id="see">查看数据</p>' : ''}
            ${type === 'combo' ? '<p class="menuNode" id="saveGroup">保存分组</p>' : ''}
            `
            return outDiv;
        },
        // 菜单的点击事件
        handleMenuClick(target: HTMLElement, item: Item, graph?: IGraph) {
            const { id } = target;
            // 删除节点
            if (id === "delete") {
                // 删除节点
                state.graph?.removeItem(item)
            }
            // 复制节点
            if (id === "copy") {
                const { label, style, labelCfg, x, y, size, type } = state.graphNodeModel
                const model = {
                    id: 'node:' + Math.random(),
                    label,
                    x: typeof size === 'number' ? x + size : x + size[0] + 10,
                    y,
                    style,
                    labelCfg,
                    type,
                    size,
                };
                // 增加节点
                state.graph?.addItem('node', model);
            }
            // 自定义数据
            if (id === 'add') {
                customForm.domain = item.getModel().custom ? JSON.parse(JSON.stringify(item.getModel().custom)) : []
                customForm.visible = true
            }
            // 查看数据
            if (id === 'see') {
                customTable.tableData = item.getModel().custom
                customTable.visible = true
            }
            // 调接口保存分组
            if (id === 'saveGroup') {

                let combo = JSON.parse(JSON.stringify(item.getModel())) // 复制当前combo

                // 获取Node
                let nodes = getNodeModel(item)
                // 获取子combo
                // let comboChild = getComboModel(item)
                // console.log(comboChild);

                delete combo.id

                let customCombo = {
                    combo: combo,
                    nodes: nodes,
                }

                localStorage.setItem('custom', JSON.stringify(customCombo))
            }
        },
    })
}
// 事件监听列表
const eventList = (graph: Graph) => {
    // 当节点直接拖动到combo中会重复触发drop事件，通过flag控制只触发一次
    let flag = 0
    // 监听拖动完成事件
    graph.on('drop', (event:any) => {

        if (flag > 0) return flag = 0

        const { dataTransfer } = event.originalEvent
        if (!dataTransfer) return
        // 落点的类型
        const t = event.item?.getModel().type
        // 获取存储的节点类型
        const node = JSON.parse(dataTransfer.getData('nodeType'))
        const { x, y } = event
        // combo
        if (node.type === 'circle' || node.type === 'rect') {

            let options: any = {
                id: `combo-${Math.random()}`,
                type: node.type,
                x,
                y
            }
            // 如果拖动combo到combo上
            if (t === 'rect' || t === 'circle') {
                flag++
                options.parentId = event.item.getModel().id
            }
            // 如果拖动到节点上会创建两个combo
            if (t && t !== 'rect' && t !== 'circle') {
                options.nodes = true
            }

            // 在画布上创建combo
            graph.createCombo(options, [])
            options.parentId ? flag++ : flag = 0
            options.nodes ? flag++ : ''
            return
        }
        // combo组
        if (node.type === 'custom') {
            // 获取保存的分组数据
            let info = JSON.parse(localStorage.getItem('custom') || '{}')
            let comboid = 'combo-' + Math.random()
            // 创建combo
            state.graph?.createCombo({ ...info.combo, id: comboid }, [])
            // // 获取combo的实例
            // const comboInstance = graph.find('combo', (c: any) => {
            //     return c.get('model').id === comboid;
            // });
            // 创建combo内的节点
            info.nodes.forEach((n: any, idx: number) => {
                let id = 'node-' + Math.random()
                graph.addItem('node', {
                    ...n,
                    x: x - n.position.x,
                    y: y - n.position.y,
                    id,
                    comboId: comboid
                })
            })
            flag = 0
            return
        }
        // node
        let id = 'node-' + Math.random()

        // 节点配置
        let option: any = {
            id, type: node.type, x, y, style: node.style, size: node.size
        }
        // 如果直接拖动到combo中
        if (t === 'rect' || t === 'circle') {
            option.comboId = event.item.getModel().id
            flag++
        }
        graph.addItem('node', option)

        option.comboId ? '' : flag = 0
    })
    /**@节点的相关事件 */
    // 鼠标进入节点
    graph.on('node:mousemove', (e:any) => {
        graph.setItemState(e.item, 'showAnchors', true);
    })
    // 鼠标离开节点
    graph.on('node:mouseout', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', false);
    })
    graph.on('node:click', (e: any) => {
        const model = e.item.getModel()
        // 存储当前点击的节点的数据
        state.graphNodeModel = model
        state.graphNode = e.item
    })
    graph.on('node:dragenter', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', true);
    })
    graph.on('node:dragleave', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', false);
    })
    graph.on('node:dragstart', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', true);
    })
    graph.on('node:dragout', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', false);

    })
    // 创建节点之后触发的回调
    graph.on('afteradditem', (e: any) => {
        if (e.item && e.item.getType() === 'edge') {
            graph.updateItem(e.item, {
                sourceAnchor: state.sourceAnchorIdx
            });
        }
        if (e.item && e.item.getType() === 'node') {
            const model = e.item.getModel()
            // 存储当前点击的节点的数据
            state.graphNodeModel = model
            state.graphNode = e.item
        }
    })
    // 节点更新之后的回调
    graph.on('afterupdateitem', (e: any) => {
        const group = e.item.getContainer()
        // 获取锚点
        const shape = group.get('children')
        const model = e.item.getModel()
        const box = group.getBBox() // 获取包围盒的坐标和尺寸
        let i = 0

        shape.forEach((item: any, index: number) => {

            if (!item.cfg.className) {

                item.attr(updateShapePosition(model, box, i))
                i++
            }
        })
    })
    /**
     * @边的相关事件
     */
    // 创建边之后触发的回调
    graph.on('aftercreateedge', (e: any) => {
        graph.updateItem(e.edge, {
            sourceAnchor: state.sourceAnchorIdx,
            targetAnchor: state.targetAnchorIdx
        })
        const edges:any = graph.save().edges;
        G6.Util.processParallelEdges(edges) // 处理平行边
        graph.getEdges().forEach((edge: any, i: any) => {
            graph.updateItem(edge, {
                curveOffset: edges[i].curveOffset,
                curvePosition: edges[i].curvePosition,
            });
        })
    })
    // 点击边的事件
    graph.on("edge:click", (e: any) => {
        state.graphNodeModel = e.item.getModel()
        state.graphNode = e.item


        // const edge = e.item;
        // const model = edge.getModel();
        // model.oriLabel = model.label;
        // 获取所有的边
        // const edges = graph.save().edges

    })
    // 经过边
    graph.on('edge:mousemove', function (e: any) {
        // 高亮显示
        e.item.setState('selected', true)
    })
    // 离开边
    graph.on('edge:mouseout', function (e: any) {
        // 取消高亮
        e.item.setState('selected', false)
    })

    /**
     * @combo的相关事件
     */
    graph.on('combo:click', (e: any) => {
        state.graphNode = e.item
        state.graphNodeModel = e.item.getModel()
    })
    graph.on('click', (e: any) => {
        state.type = e.item?.getType()
    })
}
// 普通节点类型
let icons: NodeItem[] = [
    {
        label: "圆形节点",
        type: "circle-node",
        icon: "yuanxingweixuanzhong",
        size: 40,
        style: {
            lineWidth: 2,
            cursor: "pointer"
        },
    },
    {
        label: "五角星",
        type: "star-node",
        icon: "wujiaoxingkong",
        size: 40,
        style: {
            lineWidth: 2,
            cursor: "pointer"
        },
    },
    {
        label: "长方形",
        type: "rect-node",
        icon: "tx-changfangxing",
        size: [100, 36],
        style: {
            lineWidth: 2,
            cursor: "pointer"
        },
    },
    {
        label: "三角形",
        type: "triangle-node",
        icon: "triangle",
        size: 40,
        style: {
            lineWidth: 2,
            cursor: "pointer"
        },
    },
]
// 创建扩展节点
for (let i = 0; i < icons.length; i++) {
    const item = icons[i];
    registerNode(item.type, item.type.split('-')[0])
}
onMounted(() => {
    state.graph = new G6.Graph(defaultConfig())
    // 读取初始化数据
    state.graph.data(defaultData())
    // 渲染数据
    state.graph.render()
    // 监听事件
    eventList(<Graph>state.graph)
})
// 右侧操作事件
const changeNode = (node:NodeConfig | EdgeConfig | Action) => {
    console.log(node);
    
    if (!state.graphNode) return
    // 添加文字
    if (node.type === 'text-add') {
        addText.visible = true
        return
    }
    // 其他配置
    state.graph?.updateItem(state.graphNode, (node as NodeConfig | EdgeConfig))
    state.graphNodeModel = state.graphNode.getModel()
}
// 添加文字
let addText = reactive({
    visible: false,
    text: ''
})
// 添加文字确认按钮
const saveText = () => {
    if (addText.text === '') return
    state.graph?.updateItem(state.graphNode, {
        label: addText.text
    })
    addText.visible = false
    addText.text = ''
}
// 自定义数据相关
const customForm: any = reactive({
    domain: [
        {
            key: "",
            value: ""
        }
    ],
    visible: false
})
// 添加一条表单
const addCustomInfo = () => {
    customForm.domain.push({ key: "key", value: "value" })
}
// 删除一条表单
const removeCustomItem = (i: number) => {
    customForm.domain.splice(i, 1)
}
// 保存自定义的数据
const saveCustomInfo = () => {
    state.graph?.updateItem(state.graphNode, {
        custom: customForm.domain
    })
    customForm.visible = false
}
// 查看自定义数据
const customTable: any = reactive({
    visible: false,
    tableData: []
})
// 获取当前流程图的json数据 
const saveJsonInfo = () => {
    console.log(state.graph?.save())
}
// 导出当前流程图的图片格式
const exportImageDownload = () => {
    state.graph?.downloadFullImage('graph', 'image/png')
}
</script>

<style lang="less" scope>
.container {
    width: 100vw;
    height: 100vh;
    background-color: #fff;
    position: relative;
    overflow: hidden;
}

.g6-minimap {
    position: absolute;
    right: 20px;
    bottom: 20px;
    box-shadow: 0px 0px 13px 0px #ccc;
    background-color: #fff;
    z-index: 9;

    canvas {
        position: static !important;
        z-index: 9 !important;
    }
}

.g6-grid-container {
    z-index: 0 !important;

}

#graphNode {
    canvas {
        position: absolute;
        z-index: 1;
    }
}

.g6-component-contextmenu {
    z-index: 9;
    padding: 0px !important;

    .menuNode {
        margin: 0px;
        padding: 5px 10px;
        border-radius: 4px;

        &:hover {
            background-color: #049eff !important;
            color: #fff;
        }
    }

    #delete {
        &:hover {
            background-color: #f56c6c !important;
            color: #fff;
        }
    }
}

.tooltip {
    position: fixed;
    z-index: 999;
    background-color: #000;
}

.item {
    display: flex;
    align-items: center;

    .label {
        width: 50px;
        min-width: 50px;
        text-align: center;
    }
}
</style>